计时器
简介
计时器模块可能是最基本的硬件模块之一。但即使对于计时器,您也可以使用SpinalHDL做一些有趣的事情。这个示例将定义一个简单的计时器组件,其中集成了一个总线桥接实用工具。
计时器
那么让我们从 Timer 组件开始。
规范
Timer 组件将具有一个构造参数:
| 参数名称 | 类型 | 描述 | 
|---|---|---|
| width | Int | 指定计时器计数器的位宽 | 
还有一些输入/输出:
| IO名称 | 方向 | 类型 | 描述 | 
|---|---|---|---|
| tick | in | Bool | 当  | 
| clear | in | Bool | 当  | 
| limit | in | UInt(width bits) | 当计时器值等于  | 
| full | out | Bool | 当计时器值等于  | 
| value | out | UInt(width bits) | 引出计时器的计数器值。 | 
实现
case class Timer(width : Int) extends Component {
  val io = new Bundle {
    val tick  = in Bool()
    val clear = in Bool()
    val limit = in UInt(width bits)
    val full  = out Bool()
    val value = out UInt(width bits)
  }
  val counter = Reg(UInt(width bits))
  when(io.tick && !io.full) {
    counter := counter + 1
  }
  when(io.clear) {
    counter := 0
  }
  io.full := counter === io.limit && io.tick
  io.value := counter
}
桥接函数
现在我们可以从这个例子的主要目的开始:定义总线桥接功能。为此,我们将使用两种技术:
- 使用在文档 此处 的 - BusSlaveFactory工具
- 在 - Timer组件内定义一个函数,这个函数可以从父组件调用该函数,并以抽象方式驱动- Timer的 IO。
规范
该桥接函数将使用以下参数:
| 参数名称 | 类型 | 描述 | 
|---|---|---|
| busCtrl | BusSlaveFactory | 函数将使用  | 
| baseAddress | BigInt | 桥接逻辑应映射到的基地址。 | 
| ticks | Seq[Bool] | 可用作tick信号的Bool源序列。 | 
| clears | Seq[Bool] | 可用作clear信号的Bool源序列。 | 
假设寄存器映射的总线系统位宽是32位:
| 名称 | 访问 | 位宽 | 地址偏移 | 位偏移 | 描述 | 
|---|---|---|---|---|---|
| ticksEnable | RW | len(ticks) | 0 | 0 | Each  | 
| clearsEnable | RW | len(clears) | 0 | 16 | Each  | 
| limit | RW | width | 4 | 0 | 访问计时器组件的limit值。 当写入该寄存器时,计时器清零。 | 
| value | R | width | 8 | 0 | 访问计时器的值。 | 
| clear | W | 8 | 当写入该寄存器时,计时器清零。 | 
实现
让我们在 Timer 组件中添加这个桥接函数。
case class Timer(width : Int) extends Component {
...
  // The function prototype uses Scala currying funcName(arg1,arg2)(arg3,arg3)
  // which allow to call the function with a nice syntax later
  // This function also returns an area, which allows to keep names of inner signals in the generated VHDL/Verilog.
  def driveFrom(busCtrl: BusSlaveFactory, baseAddress: BigInt)(ticks: Seq[Bool], clears: Seq[Bool]) = new Area {
    // Offset 0 => clear/tick masks + bus
    val ticksEnable = busCtrl.createReadAndWrite(Bits(ticks.length bits), baseAddress + 0,0) init(0)
    val clearsEnable = busCtrl.createReadAndWrite(Bits(clears.length bits), baseAddress + 0,16) init(0)
    val busClearing = False
    io.clear := (clearsEnable & clears.asBits).orR | busClearing
    io.tick := (ticksEnable  & ticks.asBits ).orR
    // Offset 4 => read/write limit (+ auto clear)
    busCtrl.driveAndRead(io.limit, baseAddress + 4)
    busClearing.setWhen(busCtrl.isWriting(baseAddress + 4))
    // Offset 8 => read timer value / write => clear timer value
    busCtrl.read(io.value, baseAddress + 8)
    busClearing.setWhen(busCtrl.isWriting(baseAddress + 8))
  }
}
用法
下面是一些演示代码,它与Pinsec SoC计时器模块中使用的代码非常接近。基本上,它实例化了以下元素:
- 1个16位预分频器 
- 1个32位计时器 
- 3个16位计时器 
然后,通过使用 Apb3SlaveFactory 和 Timer 内定义的函数,它在 APB3 总线和所有实例化组件之间创建桥接逻辑。
  val io = new Bundle {
    val apb = slave(Apb3(Apb3Config(addressWidth=8, dataWidth=32)))
    val interrupt = out Bool()
    val external = new Bundle {
      val tick = in Bool()
      val clear = in Bool()
    }
  }
  // Prescaler is very similar to the timer, it mainly integrates a piece of auto reload logic.
  val prescaler = Prescaler(width = 16)
  val timerA = Timer(width = 32)
  val timerB,timerC,timerD = Timer(width = 16)
  val busCtrl = Apb3SlaveFactory(io.apb)
  
  prescaler.driveFrom(busCtrl, 0x00)
  timerA.driveFrom(busCtrl, 0x40)(
    ticks=List(True, prescaler.io.overflow),
    clears=List(timerA.io.full)
  )
  timerB.driveFrom(busCtrl, 0x50)(
    ticks=List(True, prescaler.io.overflow, io.external.tick),
    clears=List(timerB.io.full, io.external.clear)
  )
  timerC.driveFrom(busCtrl, 0x60)(
    ticks=List(True, prescaler.io.overflow, io.external.tick),
    clears=List(timerC.io.full, io.external.clear)
  )
  timerD.driveFrom(busCtrl, 0x70)(
    ticks=List(True, prescaler.io.overflow, io.external.tick),
    clears=List(timerD.io.full, io.external.clear)
  )
  val interruptCtrl = InterruptCtrl(4)
  interruptCtrl.driveFrom(busCtrl, 0x10)
  interruptCtrl.io.inputs(0) := timerA.io.full
  interruptCtrl.io.inputs(1) := timerB.io.full
  interruptCtrl.io.inputs(2) := timerC.io.full
  interruptCtrl.io.inputs(3) := timerD.io.full
  io.interrupt := interruptCtrl.io.pendings.orR
}